2.1 数组
数组是我们常用的一种数据结构,在大部分编程语言中都原生支持,数组在我们的日常开发工作中也是一种必不可少的数据结构。
本节代码存放目录为 lesson2
概念及原理
什么是数组?
数组是最基础的数据结构之一,它是一个固定大小的连续内存块,用于存储相同类型的数据。
每个元素在数组中的位置可以通过索引来访问,索引从 0
开始。
数组的最大特点是:它可以通过索引在常数时间O(1)
内访问任意元素,其大小在创建时是固定的,不能动态扩展。
数组的基本结构
元素类型:数组中的所有元素必须是相同的数据类型(如整数、浮点数、字符串等)。
长度固定:数组的大小在创建时指定,一旦创建就无法改变长度。
索引访问:每个数组元素都有一个唯一的索引,访问元素时可以通过索引值直接获取数据,时间复杂度为
O(1)
。
数组的存储方式
数组是一块连续的内存区域。假设每个元素占用相同的内存空间,数组中的第一个元素存储在基地址位置,其后的元素依次存储在紧邻的内存位置上。
因此,访问数组元素时,可以通过基地址 + 索引 × 元素大小 直接定位到目标元素。
下图所示为数组存储示意图:
+------+------+------+------+------+
| 1 | 2 | 3 | 4 | 5 | <--- 数组存储在连续的内存地址中
+------+------+------+------+------+
^ ^ ^ ^ ^
| | | | |
arr[0] arr[1] arr[2] arr[3] arr[4]
从上图我们可以看出数组就是一串线性的连续存储结构,每个元素紧跟前一个元素,就跟我们排队一样。
数组的优缺点
优点:
快速访问:通过索引可以在 O(1) 时间内访问任意元素。
内存紧凑:数据存储在连续的内存区域,便于缓存优化。
缺点:
固定大小:数组的大小在创建时就需要确定,无法动态扩展或缩小。
插入和删除效率低:插入和删除操作可能需要移动多个元素,最坏情况下的时间复杂度为 O(n)。
基于数组的优缺点,我们可以大概总结出数组的应用场景:
当需要频繁通过索引访问元素时,数组是非常高效的数据结构。
数组适合存储已知数量的、同类型的元素,特别是当数组大小不会动态变化的场景。
Go语言的实现
Go数组的声明与初始化
// 声明长度为 5 的整数数组
var arr1 [5]int
// 通过字面量初始化数组
arr2 := [5]int{1, 2, 3, 4, 5}
// 自动推断数组长度
arr3 := [...]int{10, 20, 30}
在上面的代码中,我们通过多种方式声明并初始化了数组,总之就是长度肯定是固定的,如果想要了解Go
语言底层也可以点击这里进一步查看。
我们可以通过下方的示意图了解Go
语言在数组声明及初始化时具体是怎么样的:
+-----------------+-----------------+-----------------+
| arr1[0]: 0 | arr1[1]: 0 | arr1[2]: 0 | <--- 声明后默认值为 0
+-----------------+-----------------+-----------------+
| arr1[3]: 0 | arr1[4]: 0 |
+-----------------+-----------------+
+-----------------+-----------------+-----------------+
| arr2[0]: 1 | arr2[1]: 2 | arr2[2]: 3 | <--- 初始化时赋值
+-----------------+-----------------+-----------------+
| arr2[3]: 4 | arr2[4]: 5 |
+-----------------+-----------------+
Go语言中数组的操作
访问数组元素:在Go
语言中,数组的元素可以通过索引直接访问和修改。
// 访问数组中的元素
fmt.Println(arr2[2]) // 输出:3
// 修改数组中的元素
arr2[2] = 10
fmt.Println(arr2[2]) // 输出:10
遍历数组:Go语言中可以使用 for
循环和 range
关键字来遍历数组。
// 通过索引遍历数组
for i := 0; i < len(arr2); i++ {
fmt.Println(arr2[i])
}
// 使用 range 遍历数组
for i, v := range arr2 {
fmt.Printf("arr[%d] = %d\n", i, v)
}
复制数组:在Go语言中,数组是值类型,赋值时会复制整个数组。
arrCopy := arr2
arrCopy[0] = 100
fmt.Println(arr2[0]) // 输出:1 (原数组未受影响)
数组与切片的区别
在Go
语言中,数组和切片是不同的概念。数组的大小固定,而切片是一种更灵活的数据结构,它的大小可以动态变化。可以把数组看作是切片的基础,切片可以基于数组进行创建。
数组:大小固定,定义时确定。
切片:大小可变,底层使用数组实现。
切片基于数组的示意图如下所示:
+-----------------+-----------------+-----------------+-----------------+-----------------+
| arr[0]: 1 | arr[1]: 2 | arr[2]: 3 | arr[3]: 4 | arr[4]: 5 | <--- 数组
+-----------------+-----------------+-----------------+-----------------+-----------------+
| | |
v v v
+------------+ +------------+ +------------+
| slice[0]: 1| | slice[1]: 2| | slice[2]: 3| <--- 切片引用数组的部分
+------------+ +------------+ +------------+
总的来说,切片的底层就是基于数组的,我们可以通过点击这里查看到切片的详细解释。
数组的常见操作示例
我们下面以两个示例来演示数组的一些实际使用,帮助我们对数组有一个更深的了解。
示例1:查找数组中的最大元素
func findMax(arr [5]int) int {
max := arr[0]
for i := 1; i < len(arr); i++ {
if arr[i] > max {
max = arr[i]
}
}
return max
}
示例2:反转数组
func reverseArray(arr [5]int) [5]int {
for i := 0; i < len(arr)/2; i++ {
arr[i], arr[len(arr)-1-i] = arr[len(arr)-1-i], arr[i]
}
return arr
}
总结
数组是最基础且常见的数据结构之一,在Go
语言中使用数组需要注意它的固定大小特性。
尽管数组操作高效且直观,但在Go
中大多数情况下我们会使用更灵活的切片。
了解数组的基础,有助于理解切片和更复杂的数据结构,在Go
语言中很多复合结构的底层都会采用数组形式来构成。